# 
# init in Card
# 
class Card:
    def __init__( self, rank, suit ):
        self.suit = suit
        self.rank = rank
        self.hard, self.soft = self._points()

class NumberCard( Card ):
    def _points( self ):
        return int( self.rank ), int( self.rank )
    
class AceCard( Card ):
    def _points( self ):
        return 1, 11
    
class FaceCard( Card ):
    def _points( self ):
        return 10, 10
    
class Suit:
    def __init__( self, name, symbol ):
        self.name = name
        self.symbol = symbol
        
Club, Diamond, Heart, Spade = Suit( 'Club', '♣', ), Suit( "Diamond", '♦' ), Suit( 'Heart', '♥' ), Suit( 'Spade', '♠')

cards = [ AceCard( 'A', Spade ), NumberCard( '2', Spade ), NumberCard( '3', Spade )]


# factory function class return a different class
def card( rank:str, suit ):
    if rank == 1: return AceCard( 'A', suit )
    elif 2 <= rank < 11: return NumberCard( str(rank), suit )
    elif 11 <= rank < 14:
        name = { 11: 'J', 12:'Q', 13: 'K'}[rank]
        return FaceCard( name, suit )
    else:
        raise Exception( "Rank out of range" )
    
deck = [ card( rank, suit ) # create 52 cards by card factory function
        for rank in range( 1, 14 ) 
            for suit in ( Club, Diamond, Heart, Spade )]

def card4( rank:object, suit ):
    class_ = { 1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard},get( rank, NumberCard )
    return class_( rank, suit )

    from functools import partial
    part_class = {
        1: partial(AceCard, 'A'),
        11: partial(FaceCard, 'J'),
        12: partial(FaceCard, 'Q'),
        13: partial(FaceCard, 'K')
    }.get( rank, partial(NumberCard, str(rank)))
    return part_class( suit )

# Card factory Smooth interface definition and must be called in order
class CardFactory:
    def rank( self, rank ):
        self.class_, self.rank_str = {
        1: partial(AceCard, 'A'),
        11: partial(FaceCard, 'J'),
        12: partial(FaceCard, 'Q'),
        13: partial(FaceCard, 'K')
    }.get( rank, (NumberCard, str(rank)))
        return self
    def suit( self, suit ):
        return self.class_( self.rank_str, suit )
card8 = CardFactory()
deck8 = [ card8.rank(r+1).suits(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]


# 
# init in Card child class
# 
class Card:
    pass
class NumberCard( Card ):
    def __init__( self, rank, suit ):
        self.suit= suit
        self.rank= str(rank)
        self.hard = self.soft = rank
class AceCard( Card ):
    def __init__( self, rank, suit ):
        self.suit= suit
        self.rank= "A"
        self.hard, self.soft = 1, 11
class FaceCard( Card ):
    def __init__( self, rank, suit ):
        self.suit= suit
        self.rank= {11: 'J', 12: 'Q', 13: 'K' }[rank]
        self.hard = self.soft = 1

class Card:
    def __init__( self, rank, suit, hard, soft ):
        self.rank= rankself.suit= suit
        self.hard= hard
        self.soft= soft
class NumberCard( Card ):
    def __init__( self, rank, suit ):
        super().__init__( str(rank), suit, rank, rank )
class AceCard( Card ):
    def __init__( self, rank, suit ):
        super().__init__( "A", suit, 1, 11 )
class FaceCard( Card ):
    def __init__( self, rank, suit ):
        super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )
    
def card10( rank, suit ):
    if rank == 1: return AceCard( rank, suit )
    elif 2 <= rank < 11: return NumberCard( rank, suit )
    elif 11 <= rank < 14: return FaceCard( rank, suit )
    else:
        raise Exception( "Rank out of range" )
    
# 
# Simple combination of objects
# 

# Encapsulated collection class
import random
class Deck:
    def __init__( self ):
        self._cards = [ card6(r+1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]
        random.shuffle( self._cards)
    def pop(self):
        return self._cards.pop()
d = Deck()
hand = [ d.pop(), d.pop() ]

# Extend the collecion class
class Deck2(list):
    def __init__( self ):
        super().__init__(card6(r+1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
        random.shuffle(self)

d = Deck()
hand = [ d.pop(), d.pop() ]


class Deck3(list):
    def __init__(self, decks=1):
        super().__init__()
        for i in range(decks):
            self.extend( card6(r+1,s) for r in range(13) for s in (Club, Diamond, Heart, Spade) )
        random.shuffle( self )
        burn= random.randint(1,52)
        for i in range(burn): self.pop()
        
        
# 对象：私有（实现）和公有（接口）函数或属性
# python中的命名是根据函数（或属性）的目的来定义

# "{0!r}".format()和"{0!s}".format()并不会调用 __format__()方法。它们会直接调用__repr__()或者__str__()。

#  当Python遇到NotImplemented的值时，会尝试交换两个操作数的顺序
#%%
# 很难预测什么时候__del__()方法会被调用。它并不总是在使用del语句删除对象时被调用

# %%
# 目标是保证我们的程序能够和Python内置的特性无缝集成

# 例如，我们创建一个集合，最好能够让集合也实现一个__iter__()迭代器。
# 一个实现了__iter__()的集合可以与for语句很好地集成

# 以下这些情况下会考虑使用抽象基类
    # 当我们自定义类时，使用抽象基类作为基类
    # 在一个方法中使用抽象基类来确保一种操作是可行的
    # 在诊断信息或者异常中使用抽象基类来指出一种操作为什么不能生效
import collections
# dir(collections),
# dir(collections.abc.Callable.__call__)
# assert isinstance(None , collections.abc.Callable), "not a xxx"
import numbers
import decimal
isinstance(4, numbers.Number)
# decimal.Decimal类和整数/浮点数/complex类不太一样
# Decimal与所有内置的具体数值类型都没有关系
>>>issubclass(decimal.Decimal, numbers.Number )
True
>>>issubclass(decimal.Decimal, numbers.Integral )
False
>>>issubclass(decimal.Decimal, numbers.Real )
False
>>>issubclass(decimal.Decimal, numbers.Complex )
False
>>>issubclass(decimal.Decimal, numbers.Rational )
False
# %%
# 可调用对象
    # 显式地使用def语句创建函数
    # 任何定义了__call__()方法的类
    # 让这样的类成为更正规的可调用对象，我们应该让所有的可调用对象继承自collections.abc.Callable
    
#%%
# 当我们在for语句中使用一个可迭代的容器时，Python会隐式地创建一个迭代器。
x_iter = [1,2,3]
print(isinstance(x, collections.abc.Iterator ))
try:
    next(x_iter) #TypeError: 'list' object is not an iterator
except TypeError as e:
    print(e)
# %%
# Python会隐式地创建一个迭代器
for e in [1,2,3]:
    print(e)
# %%
# 通过iter()函数,creat iterator
x_iter = iter(x)
next(x_iter), next(x_iter), next(x_iter)

# %%
next(x_iter)
# %%
isinstance(x_iter, collections.abc.Iterator )
# %%
# 上下文和上下文管理器


# 3个基本的设计原则
    # 封装一个现有的容器
    # 扩展一个现有的容器
    # 创建一个全新的容器
    
#%%
import collections.abc
# 果没有继承自collections.abc.Callable基类，将非常难调试
# 错误信息对找到问题的根源更有用一些
class Power1( collections.abc.Callable ):
    def __call__( self, x, n ):
        p = 1
        for i in range(n):
            p *= x
        return p
power1 = Power1()

power1(2, 3)
# %%
class Power4( collections.abc.Callable ):
    # 递归的、可调用对象的实现
    # 把乘法计算的数量减少了一半
    def __call__( self, x, n ):
        if n == 0: return 1
        elif n % 2 == 1:
            return self.__call__(x, n-1)*x
        else: # n % 2 == 0:
            t= self.__call__(x, n//2)
            return t*t
pow4= Power4()


#%%

# 记忆化与缓存
class Power5( collections.abc.Callable ):
    def __init__( self ):
        self.memo = {}
    def __call__( self, x, n ):
        if (x,n) not in self.memo:
            if n == 0:
                self.memo[x,n]= 1
            elif n % 2 == 1:
                self.memo[x,n]= self.__call__(x, n-1) * x
            elif n % 2 == 0:
                t= self.__call__(x, n//2)
                self.memo[x,n]= t*t
            else:
                raise Exception("Logic Error")
        return self.memo[x,n]
pow5= Power5()
#%%
pow5(2,4)
# %%
pow5.memo
# %%
from functools import lru_cache
# 装饰了一个最近最少使用（LeastRecently Used，LRU）缓存
# 会把之前的请求放入缓存。请求在缓存中的大小是有限的。
# 存入最新的请求，移除最近最少使用的请求，正是LRU缓存机制的逻辑。
@lru_cache(None)
def pow6( x, n ):
    if n == 0: return 1
    elif n % 2 == 1:
        return pow6(x, n-1)*x
    else: # n % 2 == 0:
        t= pow6(x, n//2)
        return t*t
    
#%%
# 使用可调用API简化
# 一个API只专注一个方法，正是可调用对象的思路。

class B:
    def __init__(self):
        self.win = 0
        self.loss = 0
        self.stage = 0
        
    def __setattr__(self, name, value):
        if name == "win":
            self.stage = 1
            self.name = value
        elif name == "loss":
            self.stage *= 2
        super().__setattr__(name, value)
    def __call__(self):
        return self.stage

b= B()
b.win = 3
# %%
b.win

# %%
b.stage
# %%
b.loss
# %%

b.loss = 4
# %%
b.loss
# %%
b.stage
# %%
b.a = 1
# %%
b.a
# %%
class Ctr:
    def __init__(self):
        print(f"class name: {__class__.__name__}")
    def __setattr__(self, name, value):
        self.name = value
        super().__setattr__(name, value)
    def __call__(self):
        return self.name
    def __enter__(self):
        print("enter context")
        return self
    # __exit__()方法可以使用异常信息做如下两件事情
        # 通过返回一些True的值把异常吞掉
        # 通过返回其他一些False的值允许异常正常抛出。
    def __exit__(self, exc_type, ext_value, traceback):
        print("exit context")
        return self

with Ctr() as ctx:
    # ctx.a = "a"
    print(f"ctx.a")
# %%
import random
class KnownSequence:
    def __init__(self, seed=0):
        self.seed= 0
        
    def __enter__(self):
        self.was= random.getstate()
        random.seed(self.seed, version=1)
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        random.setstate(self.was)


# %%
# 浮点数只是近似值，用来进行货币计算是不妥的
# 位运算：当涉及位和字节的计算，总会使用int类型

# 复杂类型：一旦涉及复杂的数学操作，将会使用complex、float和cmath模块。可能根本不会使用Fraction或者Decimal

# 算术运算符（+、−、*、/、//、%和**等）都会映射为方法
# 355+133 计算可以写作355.__add__(133)
# 最左边的运算符决定了要使用哪个类

# A.__rsub__(B)对应的操作就是B-A，方法的实现来自右边的操作数类

# 假设实现了float的子类MyFloat
    # 表达式2.0 -MyFloat(1)，右边的操作数是左边操作数的子类
    # Python 会先尝试调用MyFloat(1).__rsub__(2.0)
    # 关键点是，子类的优先级高


#%%

"""完整性与一致性"""
# 多数对象与类之间都是简单的is-a关系
# 对象的一个基本特性就是它们可以被分类。每一个对象都属于一个类。

# 也能有acts-as的关系。
    #  如果我们把一个杯子当成陶瓷艺术品，就会考虑它的尺寸、形状和釉彩。
    # 如果把一个杯子当成纸镇，就会考虑它的重量和摩擦力。
    # 通常，这些额外的属性可以被看成是mixin类，它们定义了一个对象的附加接口或者行为
    
"""一个函数是11个属性的集合"""
# 装饰器修改函数定义，然后创建了一个新的函数
# 装饰器可以改变除__globals__和__closure__之外的其他所有属性。

"""装饰器
    改变除__globals__和__closure__之外的其他所有属性
    添加方法
    添加函数

"""

# 创建类是一组嵌套的两级过程
    # 类创建的第1阶段是带有原始定义的class语句。这个阶段会首先执行元类型，然后执行赋值语句和类中的def语句。
    # 类创建的第 2 阶段是将一个全局的类装饰器应用于类定义
    
# 当定义类时，我们的属性和方法有下面3种来源
    # class语句
    # 类级的装饰器
    # Mixin类和最后一个基类
# class语句是属性和方法的最明显来源
# 尽量用现实世界中的对象来命名基类

# class( is-a, act-as ) mixin class before base class

import functools

@functools.total_ordering
class Card:
    __slots__ = ("rank", "suit")
    def __new__(cls, rank, suit):
        self = super().__new__(cls)
        self.rank =rank
        self.suit = suit
        return self
    def __eq__(self, other):
        return self.rank == other.rank
    def __lt__(self, other):
        return self.rank < other.rank

card = Card(1, "A")
# %%
# Generator : a function that is a Singular function
# Decorator : a function that return a new function
    # 装饰器的返回值是一个被包装的函数
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

def debug2( function ):
    @functools.wraps( function )
    def logged_function( *args, **kw ):
        log= logging.getLogger( function.__name__ )
        log.debug( "call( %r, %r )", args, kw, )
        result= function( *args, **kw )
        log.debug( "result %r", result )
        return result
    return logged_function

@debug2
def ackermann( m, n ):
    if m == 0: return n+1
    elif m > 0 and n == 0: return ackermann( m-1, 1 )
    elif m > 0 and n > 0: return ackermann( m-1, ackermann( m, n-1) )

# decorator with args
def debug_named(log_name):
    def concrete_decorator(function):
        @functools.wraps( function )
        def wrapped( *args, **kw ):
            log= logging.getLogger( log_name )
            log.setLevel(logging.DEBUG)
            log.debug( "%s( %r, %r )", function.__name__, args, kw,)
            result= function( *args, **kw )
            log.debug( "%s = %r", function.__name__, result )
            return result
        return wrapped
    return concrete_decorator

@debug_named("recursion")
def ackermann( m, n ):
    if m == 0: return n+1
    elif m > 0 and n == 0: return ackermann( m-1, 1 )
    elif m > 0 and n > 0: return ackermann( m-1, ackermann( m, n-1) )

# method decorator
# 一个类中方法函数的装饰器和一个单独的函数的装饰器是一样的，只是在不同的上下文中使用。
# 这种上下文所带来的一个轻微的后果是必须经常显式地声明self变量。
def audit( method ):
    @functools.wraps(method)
    def wrapper( self, *args, **kw ):
        audit_log= logging.getLogger( 'audit' )
        before= repr(self)
        try:
            result= method( self, *args, **kw )
            after= repr(self)
        except Exception as e:
            audit_log.exception('%s before %s\n after %s', method.__qualname__, before, after )
            raise audit_log.info('%s before %s\n after %s', method.__qualname__,before, after )
            return result
        return wrapper


class Hand:
    def __init__( self, *cards ):
        self._cards = list(cards)
    @audit
    def __iadd__( self, card ):
        self._cards.append( card )
        return self
    def __repr__( self ):
        cards= ", ".join( map(str,self._cards) )
        return "{__class__.__name__}({cards})".format(__class__=self.__class__, cards=cards)

# class decorator
In [221]: def logged( class_ ):
     ...:     class_.logger= logging.getLogger( class_.__qualname__ )
     ...:     return class_
     ...: 

In [222]: @logged
     ...: class SomeClass:
     ...:     def __init__( self ):
     ...:         self.logger.info( "New thing" )
     ...:     def method( self, *args ):
     ...:         self.logger.info( "method %r", args )
     ...: 

In [223]: SomeClass()
Out[223]: <__main__.SomeClass at 0x1ee4aeb8390>

In [224]: @logged
     ...: class SomeClass:
     ...:     def __init__( self ):
     ...:         self.logger.critical ( "New thing" )
     ...:     def method( self, *args ):
     ...:         self.logger.critical( "method %r", args )
     ...: 

In [225]: SomeClass()
New thing
Out[225]: <__main__.SomeClass at 0x1ee4a743ba8>


#%%
# 向类中添加方法函数
    # 类装饰器通过两个步骤来创建新方法函数：先创建方法函数，然后将它插入到类中。
    # 通常用mixin类比用装饰器更好。一个mixin类的正确用途是用于插入方法。
    
# 添加一个标准方法的mixin类

class Memento:
    def memento(self):
        return "{0.__class__.__qualname__}({0!r})".format(self)
class SomeClass2( Memento ):
    def __init__( self, value ):
        self.value= value
    def __repr__( self ):
        return "{0.value}".format(self)
#%%
import logging
logger